import * as THREE from "three"
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from "three-mesh-bvh";

THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.InstancedMesh.prototype.raycast = acceleratedRaycast;
THREE.Raycaster.prototype.firstHitOnly = true;

const mouse = new THREE.Vector2();

/*
 *  批量的射线检测优化：
 *  1. 针对同类型事件，不会重复监听。
 *  2. 针对同类型事件，相同检测对象，不会重复射线检测。
 *  3. 更好的事件控制，可针对事件类型设置活跃状态。
 *  4. 更好的事件清除控制，射线返回 Clear 方法。
 *
 *  callbackMap : {
 *
 *                  click : {
 *
 *                           object1 : [ callback1,callback2,callback3 ....... ],
 *
 *                           object2 : [ callback1,callback2,callback3 ....... ],
 *
 *                           ....................
 *                          },
 *                  mousemove : {
 *
 *                           object1 : [ callback1,callback2,callback3 ....... ],
 *
 *                           object2 : [ callback1,callback2,callback3 ....... ],
 *
 *                           ....................
 *                          },
 *                  dblclick : {
 *
 *                           object1 : [ callback1,callback2,callback3 ....... ],
 *
 *                           object2 : [ callback1,callback2,callback3 ....... ],
 *
 *                           ....................
 *                          },
 *
 *                  ......
 *
 *                 }
 */
export class Raycaster {

    #_callbackMap = new Map();

    #_listenerMap = new Map();

    #_stateMap = new Map();

    constructor(sketchBase) {

        this.raycaster = new THREE.Raycaster();

        this.camera = sketchBase.camera;

        this.targetElement = sketchBase.targetElement;

        this.intersections = [];

        this.state = true;

        sketchBase.addToCameraChangeQueue(this.#onCameraChange);

        sketchBase.addToControlsStartQueue(() => this.state = false);

        sketchBase.addToControlsEndQueue(() => this.state = true);

        const params = {
            Mesh: {},
            Line: { threshold: 5 },
            LOD: {},
            Points: { threshold: 3 },
            Sprite: { threshold: 1 },
            Line2: { threshold: 5 },
        };

        this.params = params

        this.raycaster.params = params

    }

    /** 相机切换时候，射线检测需要切换相机 */
    #onCameraChange = (sketchBase) => {

        this.camera = sketchBase.camera;

    };

    /**
     * @param {THREE.Vector4} scissor 设置裁切
     */
    setScissor(scissor) {

        this.scissor = scissor;

    }

    /**
     * 新增射线事件
     * @param {"click"|"dblclick"|"mousemove"|"mousedown"|"mouseup"} type 射线检测事件
     * @param {THREE.Object3D | THREE.Object3D[]} object 三维对象
     * @param {(intersections: THREE.Intersection[], event: MouseEvent) => void}  callback 射线检测回调
     */
    raycast = (type, object, callback) => {

        if (!callback) return;

        if (!this.hasEvent(type)) {

            this.#_registerEvent(type);

        }

        const eventMap = this.#_callbackMap.get(type);

        let taskQueue;

        if (!eventMap.has(object)) {

            taskQueue = [];

            eventMap.set(object, taskQueue);

        } else {

            taskQueue = eventMap.get(object);

        }

        if (taskQueue.indexOf(callback) === -1) {

            taskQueue.push(callback);

        }

    };

    /**
     * 取消射线事件
     * @param {"click"|"dblclick"|"mousemove"|"mousedown"|"mouseup"} type 射线检测事件
     * @param {THREE.Object3D | THREE.Object3D[]} object 三维对象
     * @param {(intersections: THREE.Intersection[], event: MouseEvent) => void}  callback 射线检测回调
     */
    unRaycast(type, object, callback) {

        /**@type {Map<any,map>} */
        const eventMap = this.#_callbackMap.get(type);

        if (!eventMap) return;

        const taskQueue = eventMap.get(object);

        if (!taskQueue) return;

        for (let i = 0; i < taskQueue.length; i++) {

            if (taskQueue[i] === callback) {

                taskQueue.splice(i, 1);

                break;
            }
        }

        if (taskQueue.length === 0) {

            eventMap.delete(object);

        }

        if (eventMap.size === 0) {

            this.#_callbackMap.delete(type);

            const removeEventListener = this.#_listenerMap.get(type);

            removeEventListener();

            this.#_listenerMap.delete(type);

        }

    }

    /**
     * 执行一次射线检测
     * @param {"click"|"dblclick"|"mousemove"|"mousedown"|"mouseup"} type 事件类型
     * @param {THREE.Object3D | THREE.Object3D[]} object 三维对象
     * @param {(intersections: THREE.Intersection[], event: MouseEvent) => void}  callback 射线检测回调
     */
    raycastOnce(event, object, callback) {

        this.#_exe(event, object);

        const intersections = this.intersections;

        callback(intersections);

    }

    hasEvent(type) {

        return this.#_callbackMap.has(type);

    }

    #_registerEvent(type) {

        const eventMap = new Map();

        this.#_callbackMap.set(type, eventMap);

        const listener = (event) => {

            // 30帧性能
            if (type === "mousemove" && this.performance === true) {

                if (Date.now() & 1) {

                    return;

                }

            }

            eventMap.forEach((taskQueue, object) => {

                taskQueue.forEach(task => {

                    this.#_getIntersections(event, object, type);

                    const intersections = this.intersections;

                    task(intersections);

                });

            });

        };

        const removeListener = () => {

            this.targetElement.removeEventListener(type, listener);

        };

        this.#_listenerMap.set(type, removeListener);

        this.targetElement.addEventListener(type, listener);

    }

    setState(type, value) {

        if (arguments.length === 1) {

            this.state = !!type;

        } else {

            this.#_stateMap.set(type, value);

        }

    }

    getState(type) {

        if (type === undefined) {

            return this.state;

        } else if (this.#_stateMap.has(type)) {

            return this.#_stateMap.get(type);

        }

        return true;

    }

    #_getIntersections(event, object, type) {

        if (this.state === false) {

            return;

        }

        const state = this.getState(type);

        if (state === false) {

            return;

        };

        this.#_exe(event, object);

    }

    #_exe = (event, object) => {

        const { left, top, width, height } = this.targetElement.getBoundingClientRect();

        const scissor = this.scissor;

        if (scissor) {

            mouse.x = ((event.clientX - left - scissor.x) / (width - scissor.x)) * 2 - 1;

            mouse.y = -((event.clientY - top - (height - scissor.height - scissor.y)) / (scissor.height)) * 2 + 1;

        } else {

            mouse.x = ((event.clientX - left) / width) * 2 - 1;

            mouse.y = -((event.clientY - top) / height) * 2 + 1;

        }

        this.intersections.length = 0;

        const raycaster = this.raycaster;

        raycaster.setFromCamera(mouse, this.camera);

        if (Array.isArray(object)) {

            raycaster.intersectObjects(object, true, this.intersections);

        } else {

            raycaster.intersectObject(object, true, this.intersections);

        }

    };

}
